package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.*;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @program: gmall-parent
 * @author: Mr.Zhuo
 * @create: 2022-04-09 16:32
 **/
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestHighLevelClient restHighLevelClient;


    /**
     * 根据条件进行查询
     *
     * @param searchParam
     * @return
     */
    @Override
    public SearchResponseVo search(SearchParam searchParam) {

        // dsl 语句条件创建
        SearchRequest searchRequest = this.buildQueryDsl(searchParam);

        // 进行查询数据
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 数据集转换
        // 我们要返回的数据
        SearchResponseVo searchResponseVo = this.searchResult(searchResponse);

        Long total = searchResponseVo.getTotal();
        //private Integer pageSize;//每页显示的内容
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //private Integer pageNo;//当前页面
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //private Long totalPages;
        Long totalPages = (total + searchParam.getPageSize() - 1) / searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);
        return searchResponseVo;
    }


    /**
     * 查询出来的结果进行转换封装
     *
     * @param searchResponse
     * @return
     */
    private SearchResponseVo searchResult(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();

        //private Long total;//总记录数
        long total = searchResponse.getHits().getTotalHits().value;
        searchResponseVo.setTotal(total);

        //private List<Goods> goodsList = new ArrayList<>(); 商品封装数据类型
        List<Goods> goodsList = new ArrayList<>();
        SearchHit[] goodsHits = searchResponse.getHits().getHits();
        if (goodsHits != null && goodsHits.length > 0) {
            for (SearchHit goodsHit : goodsHits) {
                // 将map转化为对象格式
                Map<String, Object> sourceAsMap = goodsHit.getSourceAsMap();
                Goods goods = JSONObject.parseObject(JSONObject.toJSONString(sourceAsMap), Goods.class);
                // 细节处理 我们还需要进行高亮显示
                if (!StringUtils.isEmpty(goodsHit.getHighlightFields().get("title"))) {
                    Text text = goodsHit.getHighlightFields().get("title").getFragments()[0];

                    goods.setTitle(text.toString());
                }
                goodsList.add(goods);
            }
        }
        searchResponseVo.setGoodsList(goodsList);

        //private List<SearchResponseTmVo> trademarkList; 品牌属性进行封装
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> searchResponseTmVos = tmIdAgg.getBuckets().stream().map(bucket -> {
            // 首先设置品牌id
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            searchResponseTmVo.setTmId(Long.parseLong(bucket.getKeyAsString()));

            // 然后设置品牌属性
            Map<String, Aggregation> stringAggregationMap = bucket.getAggregations().asMap();
            // 品牌属性名赋值
            ParsedStringTerms tmNameAgg = (ParsedStringTerms) stringAggregationMap.get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            // 品牌属性值名赋值
            ParsedStringTerms tmLogoUrlAgg = (ParsedStringTerms) stringAggregationMap.get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);

            // 返回所需要的数据
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        // 然后设置 品牌属性
        searchResponseVo.setTrademarkList(searchResponseTmVos);

        // 查询平台属性值
        ParsedNested attrsAgg = (ParsedNested) aggregationMap.get("attrsAgg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");

        // 平台属性的
        //private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        List<? extends Terms.Bucket> buckets = attrIdAgg.getBuckets();
        List<SearchResponseAttrVo> searchResponseAttrVos = buckets.stream().map(bucket -> {
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();

            String key = bucket.getKeyAsString();
            // 设置平台属性id
            searchResponseAttrVo.setAttrId(Long.parseLong(key));

            // 设置平台属性 和属性值
            Map<String, Aggregation> stringAggregationMap = bucket.getAggregations().asMap();

            // 平台属性
            ParsedStringTerms attrNameAgg = (ParsedStringTerms) stringAggregationMap.get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);

            // 平台属性值
            ParsedStringTerms attrValueAgg = (ParsedStringTerms) stringAggregationMap.get("attrValueAgg");
            List<String> stringList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            searchResponseAttrVo.setAttrValueList(stringList);


            return searchResponseAttrVo;
        }).collect(Collectors.toList());

        // 设置返回参数
        searchResponseVo.setAttrsList(searchResponseAttrVos);
        return searchResponseVo;
    }

    /**
     * es条件封装
     *
     * @param searchParam
     * @return
     */
    private SearchRequest buildQueryDsl(SearchParam searchParam) {

        // 指定索引库  查询的那个{  }
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 首先判断两个入口 是否存在
        // 1入口
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            // 我们要在这里把查询条件写出来
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND));

            // 高亮查询
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            // 上面查询条件必须有我们查询的字段
            highlightBuilder.field("title");
            // 高亮的前缀
            highlightBuilder.preTags("<span style=color:red>");
            // 高亮的后缀
            highlightBuilder.postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        // 然后就是分类id值进行查询
        // 2入口
        Long category1Id = searchParam.getCategory1Id();
        Long category2Id = searchParam.getCategory2Id();
        Long category3Id = searchParam.getCategory3Id();
        if (!StringUtils.isEmpty(category1Id)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("category1Id", category1Id));
        }
        if (!StringUtils.isEmpty(category2Id)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("category2Id", category2Id));
        }
        if (!StringUtils.isEmpty(category3Id)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("category3Id", category3Id));
        }

        // 入口进来之后我们要考虑 根据品牌id进行检索 // trademark=2:华为
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            String[] trademarkStrings = trademark.split(":");
            if (trademarkStrings != null && trademarkStrings.length == 2) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", trademarkStrings[0]));
            }
        }

        // 然后紧着就是平台属性的检索 props=23:4G:运行内存
        String[] paramProps = searchParam.getProps();


        if (paramProps != null && paramProps.length > 0) {
            for (String paramProp : paramProps) {
                String[] strings = paramProp.split(":");

                if (strings != null && strings.length == 3) {
                    // 构建嵌套查询
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    // 嵌套查询子查询
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    // 给第三层进行赋值
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", strings[0]));
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", strings[1]));

                    // 给第二层赋值第三层的值
                    boolQuery.must(QueryBuilders.nestedQuery("attrs", subBoolQuery, ScoreMode.None));

                    // 给第一层进行赋值第二层的值
                    boolQueryBuilder.filter(boolQuery);
                }
            }


        }
        // 最好进行加上query查询
        searchSourceBuilder.query(boolQueryBuilder);

        // ************************下面与query是并列关系

        // 分页查询
        // 从第几个数据开始查
        Integer pageNow = (searchParam.getPageNo() - 1) * searchParam.getPageSize();
        // 从第几个开始
        searchSourceBuilder.from(pageNow);
        // 每页显示多少个
        searchSourceBuilder.size(searchParam.getPageSize());

        // 按照什么排序 order=1:desc
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)) {
            String[] strings = order.split(":");
            if (strings.length > 0 && strings != null) {
                // 分析到底是以那个数据为排序准则
                String orders = "";
                switch (strings[0]) {
                    case "1":
                        orders = "hotScore";
                        break;
                    case "2":
                        orders = "price";
                        break;
                }
                // 以什么为排序
                searchSourceBuilder.sort(orders, "desc".equals(strings[1]) ? SortOrder.DESC : SortOrder.ASC);
            } else {
                // 默认以什么排序
                searchSourceBuilder.sort("hotScore", SortOrder.DESC);
            }
        } else {
            // 默认以什么排序
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }


        // 聚合查询进行提供返回数据
        // 品牌属性进行返回
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId").size(10)
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName").size(10))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl").size(10))
        );

        // 平台属性进行返回
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrsAgg", "attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId").size(10)
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName").size(10))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));

        // 进行数据筛选返回，有些数据我们是不需要的
        searchSourceBuilder.fetchSource(new String[]{"id", "defaultImg", "title", "price"}, null);
        SearchRequest searchRequest = new SearchRequest("goods");
        searchRequest.source(searchSourceBuilder);
        System.out.println("dsl:" + searchSourceBuilder.toString());
        return searchRequest;
    }

    /**
     * 上架
     *
     * @param skuId
     */
    @Override
    public void upperGoods(Long skuId) {

        // 创建我们需要传入的属性
        Goods goods = new Goods();

        // 设置skuId
        goods.setId(skuId);

        CompletableFuture<SkuInfo> SkuInfoFuture = CompletableFuture.supplyAsync(() -> {
            // 获取skuInfo的基本信息
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);

            // 默认选择的图片
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());

            // sku的名字
            goods.setTitle(skuInfo.getSkuName());

            // sku的价格
            goods.setPrice(skuInfo.getPrice().doubleValue());

            // 时间
            goods.setCreateTime(new Date());

            // 品牌id
            goods.setTmId(skuInfo.getTmId());

            return skuInfo;
        }, threadPoolExecutor);


        CompletableFuture<Void> BaseTrademarkFuture = SkuInfoFuture.thenAcceptAsync(skuInfo -> {
            // 根据品牌id进行查询寻品牌名字
            BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
            // 品牌名字
            goods.setTmName(trademark.getTmName());

            // tmLogoUrl logo图片地址
            goods.setTmLogoUrl(trademark.getLogoUrl());
        }, threadPoolExecutor);

        CompletableFuture<Void> BaseCategoryViewFuture = SkuInfoFuture.thenAcceptAsync(skuInfo -> {
            // 获取当前sku对应的分类id
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());

            // 分类一级id
            goods.setCategory1Id(categoryView.getCategory1Id());

            //分类一级的名字
            goods.setCategory1Name(categoryView.getCategory1Name());

            // 分类二级id
            goods.setCategory2Id(categoryView.getCategory2Id());

            //分类二级的名字
            goods.setCategory2Name(categoryView.getCategory2Name());

            // 分类三级id
            goods.setCategory3Id(categoryView.getCategory3Id());

            //分类三级的名字
            goods.setCategory3Name(categoryView.getCategory3Name());

        }, threadPoolExecutor);

        CompletableFuture<Void> BaseAttrInfoFuture = CompletableFuture.runAsync(() -> {
            // 获取平台属性
            List<BaseAttrInfo> baseAttrInfos = productFeignClient.getAttrList(skuId);

            //Result<List<BaseAttrInfo>> result = productFeignClient.attrInfoList(categoryView.getCategory1Id(),
            //        categoryView.getCategory2Id(), categoryView.getCategory3Id());
            //List<BaseAttrInfo> data = result.getData();


            List<SearchAttr> searchAttrs = baseAttrInfos.stream().map(baseAttrInfo -> {
                SearchAttr searchAttr = new SearchAttr();
                // 平台属性Id
                Long attrId = baseAttrInfo.getId();

                // 平台属性id
                searchAttr.setAttrId(attrId);

                // 平台属性值名称
                String attrName = baseAttrInfo.getAttrName();
                // 平台属性name
                searchAttr.setAttrName(attrName);
                // 平台属性值name
                searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                return searchAttr;
            }).collect(Collectors.toList());

            // 进行平台属性值赋值
            goods.setAttrs(searchAttrs);
        }, threadPoolExecutor);

        CompletableFuture.allOf(SkuInfoFuture,
                BaseTrademarkFuture, BaseCategoryViewFuture, BaseAttrInfoFuture).join();

        goodsRepository.save(goods);
    }

    /**
     * 下架
     *
     * @param skuId
     */
    @Override
    public void lowerGoods(Long skuId) {
        goodsRepository.deleteById(skuId);
    }

    /**
     * 更新商品incrHotScore
     *
     * @param skuId
     */
    @Override
    public void incrHotScore(Long skuId) {
        // 调用redis进行数据增加
        String key = "hotScore";
        // 获取当redis中的数据
        Double hotScore = redisTemplate.opsForZSet().incrementScore(key, "skuId:" + skuId, 1);

        if (hotScore % 10 == 0) {
            // 每增加10次我们进行调用es进行更换数据
            Optional<Goods> optional = goodsRepository.findById(skuId);

            Goods goods = optional.get();

            // 进行热度设置
            goods.setHotScore(hotScore.longValue());

            // 放回原来的数据位置
            goodsRepository.save(goods);
        }
    }
}
